/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006-2007 Thomas E Enebo <enebo@acm.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.parser {


import asruby.lang.RubyModule;
import asruby.runtime.Arity;

/**
 * StaticScope represents lexical scoping of variables and module/class constants.
 * 
 * At a very high level every scopes enclosing scope contains variables in the next outer
 * lexical layer.  The enclosing scopes variables may or may not be reachable depending
 * on the scoping rules for variables (governed by BlockStaticScope and LocalStaticScope).
 * 
 * StaticScope also keeps track of current module/class that is in scope.  previousCRefScope
 * will point to the previous scope of the enclosing module/class (cref).
 * 
 */
public class StaticScope {
    private static var serialVersionUID: Number =  4843861446986961013;
    
    // Next immediate scope.  Variable and constant scoping rules make use of this variable
    // in different ways.
    protected  var enclosingScope:StaticScope;
    
    // Live reference to module
    private var cref:RubyModule = null;
    
    // Next CRef down the lexical structure
    private  var previousCRefScope: StaticScope =  null;
    
    // Our name holder (offsets are assigned as variables are added
    private  var variableNames:Array;
    
    private  var variableCaptured:Array;
    
    // number of variables in this scope representing required arguments
    private  var requiredArgs: int =  0;
    
    // number of variables in this scope representing optional arguments
    private  var optionalArgs: int =  0;
    
    // index of variable that represents a "rest" arg
    private  var restArg: int =  -1;
    
    // Whether this scope is used as the "argument scope" for e.g. zsuper
    private  var _isArgumentScope: Boolean =  false;
    
    public function StaticScope(enclosingScope:StaticScope, names:Array=null){
        this.enclosingScope = enclosingScope;
        this.variableNames = names;
        this.variableCaptured = new Array;
    }
    
    public  function addVariable(name:String):int{
        var slot: int =  this.isDefined(name); 

        if (slot >= 0) return slot;
            
        // This is perhaps innefficient timewise?  Optimal spacewise
        if (variableNames == null) {
            variableNames = new String[1];
            variableCaptured = new Array();
            variableNames[0] = name;
        } else {
            var newVariableNames: Array = variableNames.slice(0, variableNames.length);
            variableNames = newVariableNames;
            variableNames[variableNames.length - 1] = name;
            var newVariableCaptured: Array = variableCaptured.slice(0, variableCaptured.length);
            variableCaptured = newVariableCaptured;
        }
        
        // Returns slot of variable
        return variableNames.length - 1;
    }
    
    public  function getVariables():Array{
        return variableNames;
    }
    
    public  function getNumberOfVariables():int{
        return variableNames == null ? 0 : variableNames.length;
    }
    
    public  function setVariables(names:Array):void{
        if (names == null) return;
        
        variableNames = names.slice(0, names.length);
        variableCaptured = new Array;
    }
    
    /**
     * Next outer most scope in list of scopes.  An enclosing scope may have no direct scoping
     * relationship to its child.  If I am in a localScope and then I enter something which
     * creates another localScope the enclosing scope will be the first scope, but there are
     * no valid scoping relationships between the two.  Methods which walk the enclosing scopes
     * are responsible for enforcing appropriate scoping relationships.
     * 
     * @return the parent scope
     */
    public  function getEnclosingScope():StaticScope{
        return enclosingScope;
    }
    
    /**
     * Does the variable exist?
     * 
     * @param name of the variable to find
     * @return index of variable or -1 if it does not exist
     */
    public  function exists(name:String):int{
        if (variableNames != null) {
            for ( var i:int = 0; i < variableNames.length; i++) {
                if (name == variableNames[i]) return i;
            }
        }
        
        return -1;        
    }
    
    /**
     * Is this name in the visible to the current scope
     * 
     * @param name to be looked for
     * @return a location where the left-most 16 bits of number of scopes down it is and the
     *   right-most 16 bits represents its index in that scope
     */
    public  function isDefined(name:String):int{
        return this.isDefined2(name, 0);
    }
    
    /**
     * Make a DASgn or LocalAsgn node based on scope logic
     * 
     * @param position
     * @param name
     * @param value
     * @return
     */
/*     public  function assign(position:int, name:String, value:Node):AssignableNode{
        return this.assign(position, name, value, this, 0);
    } */
    
    /**
     * Get all visible variables that we can see from this scope that have been assigned
     * (e.g. seen so far)
     * 
     * @return a list of all names (sans $~ and $_ which are special names)
     */
    public function getAllNamesInScope():Array { return null;};
    
    public function isDefined2(name:String, depth:int):int {return 0;}
  /*   public function assign2(position:int, name:String, value:Node, 
             topScope:StaticScope,  depth:int):AssignableNode {return null;}
    public function declare2(position:int, name:String, depth:int):Node {return null;}
     */
    /**
     * Make a DVar or LocalVar node based on scoping logic
     * 
     * @param position the location that in the source that the new node will come from
     * @param name of the variable to be created is named
     * @return a DVarNode or LocalVarNode
     */
 /*    public  function declare(position:int, name:String):Node{
        return declare2(position, name, 0);
    } */
    
    public  function capture(index:int):void{
        variableCaptured[index] = true;
    }
    
    public  function isCaptured(index:int):Boolean{
        return variableCaptured[index];
    }

    /**
     * Gets the Local Scope relative to the current Scope.  For LocalScopes this will be itself.
     * Blocks will contain the LocalScope it contains.
     * 
     * @return localScope
     */
    public function getLocalScope():StaticScope {return null};
    
    /**
     * Get the live CRef module associated with this scope.
     * 
     * @return the live module
     */
    public  function getModule():RubyModule{
        return cref;
    }
    
    public  function getPreviousCRefScope():StaticScope{
        return previousCRefScope;
    }

    public  function setModule(module:RubyModule):void{
        this.cref = module;
        
        if (previousCRefScope == null) {
            for ( var scope:StaticScope = getEnclosingScope(); scope != null; scope = scope.getEnclosingScope()) {
                if (scope.cref != null) {
                    previousCRefScope = scope;
                    return;
                }
            }
        }
    }

    /**
     * Update current scoping structure to populate with proper cref scoping values.  This should
     * be called at any point when you reference a scope for the first time.  For the interpreter
     * this is done in a small number of places (defnNode, defsNode, and getBlock).  The compiler
     * does this in the same places.
     *
     * @return the current cref, though this is largely an implementation detail
     */
    public  function determineModule():RubyModule{
        if (cref == null) {
            cref = getEnclosingScope().determineModule();
            
            //assert cref != null : "CRef is always created before determine happens";
            
            previousCRefScope = getEnclosingScope().previousCRefScope;
        }
        
        return cref;
    }

    public  function getOptionalArgs():int{
        return optionalArgs;
    }

    public  function setOptionalArgs(optionalArgs:int):void{
        this.optionalArgs = optionalArgs;
    }

    public  function getRequiredArgs():int{
        return requiredArgs;
    }

    public  function setRequiredArgs(requiredArgs:int):void{
        this.requiredArgs = requiredArgs;
    }

    public  function getRestArg():int{
        return restArg;
    }

    public  function setRestArg(restArg:int):void{
        this.restArg = restArg;
    }
    
    public  function isArgumentScope():Boolean{
        return _isArgumentScope;
    }
    
    public  function setArgumentScope(isArgumentScope:Boolean):void{
        this._isArgumentScope = isArgumentScope;
    }
    
    public  function getArity():Arity{
        if (optionalArgs > 0) {
            if (restArg >= 0) {
                return Arity.optional();
            }
            return Arity.required(requiredArgs);
        } else {
            if (restArg >= 0) {
                return Arity.optional();
            }
            return Arity.fixed(requiredArgs);
        }
    }
    
    public  function setArities(required:int, optional:int, rest:int):void{
        this.requiredArgs = required;
        this.optionalArgs = optional;
        this.restArg = rest;
    }
}
}